/******************************************************************************* 
 * Copyright (c) 2008 xored software, Inc.  
 * 
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution, and is available at 
 * http://www.eclipse.org/legal/epl-v10.html  
 * 
 * Contributors: 
 *     xored software, Inc. - initial API and Implementation (Yuri Strot) 
 *******************************************************************************/
package com.xored.glance.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.swt.custom.StyleRange;

/**
 * @author Yuri Strot
 * 
 */
public class StyleList {

	public StyleList() {
		init();
	}

	public StyleList(StyleList list) {
		init(list);
	}

	public StyleList(StyleRange style) {
		init(style);
	}

	public StyleList(StyleRange[] styles) {
		init(styles);
	}

	protected StyleList(int[] index, StyleRange[] styles, int length) {
		init(index, styles, length);
	}

	protected void init() {
		index = new int[0];
		styles = new StyleRange[0];
	}

	protected void init(StyleList list) {
		index = new int[list.index.length];
		styles = new StyleRange[list.styles.length];
		System.arraycopy(list.index, 0, index, 0, list.index.length);
		System.arraycopy(list.styles, 0, styles, 0, list.styles.length);
	}

	protected void init(StyleRange style) {
		if (style == null) {
			init();
		} else {
			index = new int[2];
			styles = new StyleRange[1];
			index[0] = style.start;
			index[1] = style.start + style.length;
			styles[0] = copy(style);
		}
	}

	protected void init(StyleRange[] styles) {
		if (styles == null || styles.length == 0) {
			init();
		} else if (styles.length == 1) {
			init(styles[0]);
		} else {
			int[] tempIndex = new int[styles.length * 2];
			StyleRange[] tempStyles = new StyleRange[styles.length * 2];
			StyleRange style = styles[0];
			tempIndex[0] = style.start;
			tempIndex[1] = style.start + style.length;
			tempStyles[0] = copy(style);
			int cur = 2;
			for (int i = 1; i < styles.length; i++) {
				style = styles[i];
				if (tempIndex[cur - 1] > style.start)
					throw new IllegalArgumentException("Styles can't overlap");
				if (tempIndex[cur - 1] < style.start) {
					tempStyles[cur - 1] = null;
					tempIndex[cur] = style.start;
					cur++;
				}
				tempStyles[cur - 1] = copy(style);
				tempIndex[cur] = style.start + style.length;
				cur++;
			}
			init(tempIndex, tempStyles, cur);
		}
	}

	protected void init(int[] index, StyleRange[] styles, int length) {
		int[] tempIndex = new int[length];
		StyleRange[] tempStyles = new StyleRange[length - 1];

		int start = 0;
		while (start < styles.length - 1 && styles[start] == null)
			start++;

		int end = styles.length - 1;
		while (end >= 0 && styles[end] == null)
			end--;

		if (start > end) {
			init();
			return;
		}

		tempIndex[0] = index[start];
		tempStyles[0] = styles[start];

		int i = start + 1, j = 1;
		for (; i <= end; i++) {
			if (!equals(styles[i], styles[i - 1])) {
				tempIndex[j] = index[i];
				tempStyles[j] = styles[i];
				j++;
			}
		}
		tempIndex[j] = index[i];

		if (j + 1 == index.length) {
			this.index = tempIndex;
			this.styles = tempStyles;
		} else {
			this.index = new int[j + 1];
			this.styles = new StyleRange[j];
			System.arraycopy(tempIndex, 0, this.index, 0, j + 1);
			System.arraycopy(tempStyles, 0, this.styles, 0, j);
		}
	}

	protected boolean equals(StyleRange range1, StyleRange range2) {
		if (range1 == null)
			return range2 == null;
		return range1.similarTo(range2);
	}

	protected StyleRange copy(StyleRange style) {
		StyleRange newRange = new StyleRange(style);
		newRange.fontStyle = style.fontStyle;
		return newRange;
	}

	protected StyleRange getStyle(StyleRange[] styles, int pos) {
		if (pos < 0 || pos >= styles.length)
			return null;
		return styles[pos];
	}

	protected int getIndex(int[] index, int pos) {
		if (pos >= index.length)
			return Integer.MAX_VALUE;
		return index[pos];
	}

	public StyleList applyStyles(StyleList list) {
		if (list.index.length == 0) {
			return new StyleList(this);
		}
		if (index.length == 0) {
			return new StyleList(list);
		}

		int[] index1 = index;
		StyleRange[] styles1 = styles;
		int[] index2 = list.index;
		StyleRange[] styles2 = list.styles;

		int[] resultIndex = new int[index1.length + index2.length];
		StyleRange[] resultStyles = new StyleRange[resultIndex.length];

		int i = 0, j = 0, k = 0;
		for (; i < index1.length || j < index2.length; k++) {
			int index1i = getIndex(index1, i), index2j = getIndex(index2, j);
			StyleRange style1, style2;
			if (index1i < index2j) {
				resultIndex[k] = index1i;
				style1 = getStyle(styles1, i);
				style2 = getStyle(styles2, j - 1);
				i++;
			} else if (index1i > index2j) {
				resultIndex[k] = index2j;
				style1 = getStyle(styles1, i - 1);
				style2 = getStyle(styles2, j);
				j++;
			} else {
				resultIndex[k] = index2j;
				style1 = getStyle(styles1, i);
				style2 = getStyle(styles2, j);
				j++;
				i++;
			}
			if (style2 != null)
				resultStyles[k] = style2;
			else
				resultStyles[k] = style1;
		}

		return new StyleList(resultIndex, resultStyles, k);
	}

	public StyleList subStyles(int start, int end) {
		return subStyles(start, end, 0);
	}

	public StyleList subStyles(int start, int end, int offset) {
		if (start >= end)
			throw new IndexOutOfBoundsException(start + " should be less than "
					+ end);
		if (index.length == 0)
			return new StyleList();
		int first = index[0], last = index[index.length - 1];
		if (start < first)
			start = first;
		if (end > last)
			end = last;
		if (start == end)
			return new StyleList();
		int index1 = Arrays.binarySearch(index, start);
		if (index1 >= 0)
			index1++;
		else
			index1 = -1 * index1 - 1;

		int index2 = Arrays.binarySearch(index, end);
		if (index2 >= 0)
			index2--;
		else
			index2 = -1 * index2 - 2;

		int length = index2 - index1 + 1;
		if (length < 0)
			length = 0;
		int[] resultIndex = new int[length + 2];
		StyleRange[] resultStyles = new StyleRange[length + 1];

		resultIndex[0] = start;
		resultIndex[length + 1] = end;
		resultStyles[0] = getStyle(styles, index1 - 1);
		if (length > 0) {
			System.arraycopy(index, index1, resultIndex, 1, length);
			if (length > 1) {
				System.arraycopy(styles, index1, resultStyles, 1, length);
			}
		}

		for (int i = 0; i < resultIndex.length; i++) {
			resultIndex[i] -= offset;
		}

		return new StyleList(resultIndex, resultStyles, resultIndex.length);
	}

	public StyleString[] getStyleStrings(String text) {
		if (index.length == 0)
			return new StyleString[] { new StyleString(text) };
		int lastIndex = index[index.length - 1];
		if (lastIndex >= text.length())
			throw new ArrayIndexOutOfBoundsException(index[index.length - 1]);

		List<StyleString> strings = new ArrayList<StyleString>(index.length + 1);
		if (index[0] > 0)
			strings.add(new StyleString(text.substring(0, index[0])));
		for (int i = 0; i < index.length - 1; i++) {
			String string = text.substring(index[i], index[i + 1]);
			strings.add(new StyleString(string, styles[i]));
		}
		if (lastIndex < text.length() - 1)
			strings.add(new StyleString(text.substring(lastIndex)));
		return strings.toArray(new StyleString[strings.size()]);
	}

	public StyleRange[] getStyles() {
		List<StyleRange> ranges = new ArrayList<StyleRange>(index.length);
		for (int i = 0; i < index.length - 1; i++) {
			if (styles[i] != null) {
				StyleRange range = copy(styles[i]);
				range.start = index[i];
				range.length = index[i + 1] - index[i];
				ranges.add(range);
			}
		}
		return ranges.toArray(new StyleRange[ranges.size()]);
	}

	@Override
	public int hashCode() {
		return index.hashCode() ^ styles.hashCode();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		if (obj instanceof StyleList) {
			StyleList list = (StyleList) obj;
			return Arrays.equals(index, list.index)
					&& Arrays.equals(styles, list.styles);
		}
		return false;
	}

	private int[] index;
	private StyleRange[] styles;

}
